package com.slatch;

import java.util.ArrayList;
import java.util.List;
import java.util.PriorityQueue;
import java.util.Stack;
import java.util.Timer;

import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.graphics.Color;
import android.graphics.Point;
import android.os.Build;
import android.os.Handler;
import android.util.Log;
import android.widget.TextView;


public class Moteur {
	Unite uniteD; // unite en attente de déplacement
    Unite uniteA; // unite en attente d'attaque
    boolean[][] tabAtt;
    
    int[][] tabDist;
    Point[][] pred;
    
    static Point[] voisins = {new Point(0,1), new Point(0,-1),new Point(1,0),new Point(-1,0)};
    Quad[] signes = {new Quad(0,1,-1,1), new Quad(0,-1,1,-1),new Quad(1,-1,0,1),new Quad(-1,1,0,-1)};//, new Point(0,1), new Point(0,-1), new Point(-1,0), new Point(1,0)};
    
    public Moteur()
    {
    	tabDist = new int[Slatch.partie.getanombrecaseX()][Slatch.partie.getanombrecaseY()];
        pred = new Point[Slatch.partie.getanombrecaseX()][Slatch.partie.getanombrecaseY()];
        tabAtt= new boolean[Slatch.partie.getanombrecaseX()][Slatch.partie.getanombrecaseY()];

        uniteD = null;
        uniteA = null;
    }
    
    public void creationUnite(final int pX,final int pY, final TypeUnite pType){
        int vNumJoueur = Slatch.partie.getJoueurActuel();
        Joueur vJoueur = Slatch.partie.getJoueur(vNumJoueur);
        vJoueur.addArgent(-pType.getPrix());
        Unite creation = new Unite(pX,pY,vNumJoueur,pType);
        creation.deplacee(true);
        creation.attaque(true);
        Slatch.partie.getTerrain()[pX][pY].setUnite(creation);
        vJoueur.getListeUnite().add(creation);
        Log.d("Moteur_creationUnite","Unite:"+pType.getNom()+" X:"+pX+" Y:"+pY);
        MainActivity.view.invalidate();
    }
    
    /*
     * Appelee par l'IHM quand on clique sur une case, cette methode doit generer la liste des coordonnees accessibles par l'unite se trouvant sur la case selectionnee si elle ne s'est pas deja deplacee, et passer cette Liste a l'IHM.
     */
    public void caseSelectionnee(int pX, int pY)
    {
    	if(Slatch.partie.getTerrain()[pX][pY].getSurbrillance() && uniteA==null && uniteD == null) {
            this.enleverSurbrillance();
            return;
        }
    	
    	this.enleverSurbrillance();
    	
    	if(uniteA==null && uniteD == null)
            Slatch.partie.getTerrain()[pX][pY].setSurbrillance(true);
    	
    	Unite unite = Slatch.partie.getTerrain()[pX][pY].getUnite();
    	
    	if(unite==null) // si aucune unite n'est presente sur la case
        {
    		annulerAttaque();
    		if(uniteD!=null) // si on a selectione aucune unite auparavant pour le deplacement
            {
    			Slatch.partie.getTerrain()[uniteD.getX()][uniteD.getY()].setPV(Slatch.partie.getTerrain()[uniteD.getX()][uniteD.getY()].getType().getPVMax());
                deplacement(uniteD, pX, pY);
                Slatch.partie.getTerrain()[pX][pY].setSurbrillance(true);
            }
    		else if(Slatch.partie.getTerrain()[pX][pY].getType()==TypeTerrain.USINE && Slatch.partie.getTerrain()[pX][pY].getJoueur()==Slatch.partie.getJoueurActuel())
            {
                List<TypeUnite> l = new ArrayList<TypeUnite>();
                for(TypeUnite vType: Slatch.partie.getJoueur(Slatch.partie.getJoueurActuel()).getFaction().getListe())
                {
                    if(vType.nomType.equals("Terrestre")){l.add(vType);}
                }
                MainActivity.view.shop(l,Slatch.partie.getJoueur(Slatch.partie.getJoueurActuel()).getArgent(), pX, pY);
            }
    		annulerDeplacement();
        }
        else
        {
            if(uniteA==null) // si on a sélectionné aucune unité auparavant pour l'attaque
            {
                if(uniteD==null)
                {   if(Slatch.partie.getJoueurActuel()==unite.getJoueur())
                    {
                        List<String> items= new ArrayList<String>();//on va afficher le menu en créant une liste d'items
                        if(!unite.dejaDeplacee())
                        {
                        	items.add("Deplace");
                        	Log.d("Moteur_caseSelectionnee","Deplace OK");
                        }
                        
                        if(cibleEnVue(unite, false) && !unite.dejaAttaque())
                        {
                            if(unite.getType()!=TypeUnite.INGENIEUR)
                            {
                            	items.add("Attaque");
                            	Log.d("Moteur_caseSelectionnee","Attaque OK");
                            }
                        }
                        if(unite.getType()==TypeUnite.INGENIEUR)
                        {
                            if(cibleSoignable(unite) && !unite.dejaAttaque())
                            {
                                items.add("Soin");
                                Log.d("Moteur_caseSelectionnee","Soin OK");
                            }
                        }
                        if(!unite.dejaAttaque()&&(unite.getType()==TypeUnite.COMMANDO || unite.getType()==TypeUnite.DEMOLISSEUR) && (Slatch.partie.getTerrain()[pX][pY].getType()==TypeTerrain.BATIMENT || Slatch.partie.getTerrain()[pX][pY].getType()==TypeTerrain.USINE) && Slatch.partie.getJoueurActuel()!=Slatch.partie.getTerrain()[pX][pY].getJoueur())
                        {
                                items.add("Capture");
                                Log.d("Moteur_caseSelectionnee","Capture OK");
                        }
                        Slatch.partie.getTerrain()[pX][pY].setSurbrillance(true);
                        if(!items.isEmpty()) {MainActivity.view.afficheMenu(items, pX, pY);}
                    }
                }
                else
                {
                    annulerDeplacement();
                }
            }
            else
            {
            	if(unite.getJoueur()!=uniteA.getJoueur() && uniteA.getAttaque().efficacite.containsKey(unite.getType()) && tabAtt[pX][pY]) // si l'unité ciblée n'appartient pas au même joueur que l'attaquant, et que l'attaquant a une attaque qui peut toucher la cible, alors on attaque
                {
            		Log.d("Moteur_caseSelectionnee","Attaque");
                    attaque(unite);
                }
                else
                {
                    if(unite.getJoueur()==uniteA.getJoueur())
                    {
                        annulerAttaque();
                    }
                }
            }
        }
    }
    
    /**
     * Verifie si une unite se trouve a portee de tir de l'unite passee en parametre
     * @param unite unite qui cherche une autre unite a frapper
     * @return true si une unite est a portee de tir, false sinon
     */
    private boolean cibleEnVue(Unite unite, boolean soin)
    {
    	for(int i=1; i<=unite.getAttaque().aTypePortee.getPorteeMax(); i++)
        {
            for(int j=1; j<=i; j++)
            {
                for(Quad q: signes)
                {      
                    if(ciblePresente(unite, i*q.a+j*q.b,i*q.c+j*q.d, soin)){return true;}
                }
            }
        }
        return false;
    }
    
    public void modeSoin(int pX, int pY)
    {
        uniteA = Slatch.partie.getTerrain()[pX][pY].getUnite();
        affichePorteeAttaque(uniteA, true);
    }
    
    /**
     * Methode a deplacer dans Moteur Une fois moteur safe
     * Methode qui permet a un ingenieur de soigner une unite
     * @param pUnite
     */
    public void soin(Unite pUnite)
    { 
        if(pUnite.soigner(5)){            
           //On "grise" l'unité qui soigne
           uniteA.addExperience(10);
           uniteA.attaque(true);
           uniteA.deplacee(true);
           uniteA=null; 
        }
    }
    
    public boolean cibleSoignable(Unite unite)
    {
        int x;
        int y;
        for(Point p: voisins)
        {
            x=p.x;
            y=p.y;
            if(dansLesBords(x,y))
            {
                Unite u = Slatch.partie.getTerrain()[x+unite.getX()][y+unite.getY()].getUnite();
                if(u!= null)
                {
                    if(u.aBesoinDeSoins())
                    {
                        return true;
                    }
                }
            }
        }
        return false;
    }
    
    private boolean dansLesBords(int x, int y)
    {
        return(x>=0 && y>=0 && x<Slatch.partie.getanombrecaseX() && y<Slatch.partie.getanombrecaseY());
    }
    
    /**
     * vérifie si une cible est présente en (x+decX, y+decY)
     */
    private boolean ciblePresente(Unite unite, int decX, int decY, boolean soin)
    {
        int x = unite.getX();
        int y = unite.getY();
        
        if(x+decX<Slatch.partie.getanombrecaseX() && y+decY<Slatch.partie.getanombrecaseY() && x+decX>=0 && y+decY>=0)
        {
            if(distance(x+decX, y+decY, x,y)>=unite.getAttaque().aTypePortee.getPorteeMin() && distance(x+decX, y+decY, x,y)<=unite.getAttaque().aTypePortee.getPorteeMax() && distance(x+decX, y+decY, x,y)>=unite.getAttaque().aTypePortee.getPorteeMin() && Slatch.partie.getTerrain()[x+decX][y+decY].getUnite()!=null)
            {
                return (Slatch.partie.getTerrain()[x+decX][y+decY].getUnite().getJoueur()!=Slatch.partie.getJoueurActuel()^soin)&&!(unite.dejaDeplacee() && distance(x+decX, y+decY, x,y)>=2);
            }
        }
        return false;
    }
    
    public void annulerDeplacement() {
        uniteD=null;
    }
    
    public void annulerAttaque() {
        uniteA=null;
    }
    
    /*
     * permet de déplacer une unité en fonction du chemin passé en paramètre
     */
	public void deplacement(Unite unite, int pX, int pY)
     {  
		 boolean fini = false;
	     int x = pX, y =pY;
	     Stack<Point> chemin = new Stack<Point>();
	     
	     if(pred[x][y]!=null){chemin.push(new Point(pX,pY));}
	     while(!fini)
	     {
	         Point p = pred[x][y];
	         if(p!=null)
	         {
	             x=p.x;
	             y=p.y;
	             if(unite.getX()==x && unite.getY()==y)
	             {
	                 fini = true;
	             }
	             else
	             {
	                 chemin.push(p);
	             }
	         }
	         else{break;}
	     }
	     final int k = chemin.size();
	     int l = unite.getType().getDeplacement();
	     Unite mem = null;
	     while(!chemin.isEmpty())
	     {
	         Point p = chemin.pop();
	         if((l-=Slatch.partie.getTerrain()[p.x][p.y].getCout(unite))<0)
	         {
	             break;
	         }
	         mem = changerCase(unite, p.x, p.y, mem);

	         try{
	             Thread.sleep(250/k+50);
	             
	         }
	         catch(InterruptedException e)
	         {
	             e.printStackTrace();
	         }
	         
	     }
	     unite.deplacee(true);
     }
    
    /**
     * Deplace une unite vers sa destination
     * @param unite unite a deplacer
     * @param pX abscisse de l'arrivee
     * @param pY ordonnee de l'arrivee
     */
	public Unite changerCase(Unite unite, int destX, int destY, Unite mem)
    {
		if(unite.getX() != destX) {
			for(int i=0;i<MainActivity.view.getAlargeurcasePX() ;i++) {
				MainActivity.view.setDecaleUniteX(i);
				MainActivity.view.invalidate();
			}
		}
		
		if(unite.getY() != destY) {
			for(int i=0;i<MainActivity.view.getAhauteurcasePX() ;i++) {
				MainActivity.view.setDecaleUniteY(i);
				MainActivity.view.invalidate();
			}
		}

		MainActivity.view.setDecaleUniteX(0);
		MainActivity.view.setDecaleUniteY(0);
		
        Unite ret=Slatch.partie.getTerrain()[destX][destY].getUnite();
        Slatch.partie.getTerrain()[unite.getX()][unite.getY()].setUnite(mem);
        unite.setX(destX); unite.setY(destY);
        Slatch.partie.getTerrain()[destX][destY].setUnite(unite);

        MainActivity.view.invalidate();
        
        return ret;
    }

    public void enleverSurbrillance() {
        for(int i=0; i<Slatch.partie.getanombrecaseX(); i++)
        {
            for(int j=0; j<Slatch.partie.getanombrecaseY(); j++)
            {
                if(Slatch.partie.getTerrain()[i][j].getSurbrillance())
                {
                    Slatch.partie.getTerrain()[i][j].setSurbrillance(false);
                }
            }
        }
    }
    
    public void modeDeplacement(int pX, int pY) {
        uniteD = Slatch.partie.getTerrain()[pX][pY].getUnite();
        affichePorteeDep(uniteD);
    }
    
    public void remplitPorteeDep(Unite unite, boolean bool)
    {
        this.initialiseTabDist(unite.getX(), unite.getY());
        this.algoDeplacement(unite, bool);
    }
    
    private void algoDeplacement(Unite unite, boolean porteeComptee)
    {
    	Log.d("Moteur_algoDeplacement","Unite X:"+unite.getX()+" Y:"+unite.getY());
        PriorityQueue<Triplet> pq = new PriorityQueue<Triplet>();
        pq.add(new Triplet(0,unite.getX(),unite.getY()));
        while(!pq.isEmpty())
        {
            Triplet t = pq.poll();
            for(Point p: voisins)
            {
            	int x = t.x+p.x;
                int y = t.y+p.y;
                if(x>=0 && y>=0 && x<Slatch.partie.getanombrecaseX() && y<Slatch.partie.getanombrecaseY())
                {
                	
                    int d = t.d+Slatch.partie.getTerrain()[x][y].getCout(unite);
                    if(d<=unite.getType().getDeplacement() || !porteeComptee)
                    {
                        if(d<tabDist[x][y] || tabDist[x][y]==-1)
                        {
                            tabDist[x][y] = d;
                            pred[x][y] = new Point(t.x, t.y);
                            pq.offer(new Triplet(d, x, y));
                        }
                    }
                }
            }
        }
    }
    
    public void affichePorteeDep(Unite unite) {
    	this.remplitPorteeDep(unite, true);
        for(int i=0; i<Slatch.partie.getanombrecaseX(); i++)
        {
            for(int j=0; j<Slatch.partie.getanombrecaseY(); j++)
            {
                if(Slatch.partie.getTerrain()[i][j].getUnite()!=null)
                {
                    if(Slatch.partie.getTerrain()[i][j].getUnite().getJoueur() == Slatch.partie.getTerrain()[unite.getX()][unite.getY()].getUnite().getJoueur())
                    {
                            tabDist[i][j] = -2;
                    }
                }
                if(tabDist[i][j]>0)
                {
                    Slatch.partie.getTerrain()[i][j].setSurbrillance(true);
                    //MainActivity.view.invalidate();
                }
            }
        }
    
    }
    

    /**
     * Initialise tabDist afin d'utiliser algoDeplacement dans des conditions optimales
     */
    private void initialiseTabDist(int x, int y)
    {
        for(int i=0; i<Slatch.partie.getanombrecaseX(); i++)
        {
            for(int j=0; j<Slatch.partie.getanombrecaseY(); j++)
            {
                if(Slatch.partie.getTerrain()[i][j].getUnite()!=null)  // quand on a déjà une unité sur la case, on ne peut pas y accéder
                {
                    if(Slatch.partie.getTerrain()[i][j].getUnite().getJoueur() != Slatch.partie.getTerrain()[x][y].getUnite().getJoueur())
                    {
                        tabDist[i][j] = -2;
                    }
                    else
                    {
                        tabDist[i][j] = -1;
                    }
                }
                else{tabDist[i][j] = -1;} // au début, on suppose qu'on a une distance infinie représentée par -1 sur chacune des cases restantes
                pred[i][j]=null;
            }
        }
        tabDist[x][y]=-2;
        pred = new Point[Slatch.partie.getanombrecaseX()][Slatch.partie.getanombrecaseY()];
    }
    
    public void passeTour()
    {
        Slatch.partie.tourSuivant();
        List<Unite> l = Slatch.partie.getJoueur(Slatch.partie.getJoueurActuel()).getListeUnite();
        
        for(Unite u: l)
        {
            u.attaque(false);
            u.deplacee(false);
        }
    }
    
    public void attaque(Unite pVictime)
    { 
    	Log.d("Moteur_attaque","Victime X:"+pVictime.getX()+" Y:"+pVictime.getY());
    	double degatsAtt=0;
        //degatsAtt=(uniteA.getAttaque().getDegats()*uniteA.getAttaque().efficacite.get(pVictime.getType()))*(100-(TypeTerrain.bonusCouverture*Slatch.partie.getTerrain()[pVictime.getCoordonneeX()][pVictime.getCoordonneeY()].getType().getCouverture()))/100;
        degatsAtt = getDegats(uniteA, pVictime);
        //pVictime.setPointDeVie(pVictime.getPointDeVie() - (int)degatsAtt);
        if(faireDegats(pVictime, degatsAtt)) // si la victime meurt
        {
        	//Pour les statistiques
            uniteA.addExperience(degatsAtt+pVictime.getPV());
            Slatch.partie.getJoueur(uniteA.getJoueur()).addExpTotal(degatsAtt+pVictime.getPV());
            Slatch.partie.getJoueur(uniteA.getJoueur()).addDegatTotal(degatsAtt+pVictime.getPV());
            Slatch.partie.getJoueur(pVictime.getJoueur()).addDegatSubit(degatsAtt+pVictime.getPV());
            Slatch.partie.getJoueur(uniteA.getJoueur()).addNbrUniteTue();
            
        	
        	estMort(pVictime);
        }    
        else if(distance(uniteA, pVictime)==1 && pVictime.getAttaque().aTypePortee.getPorteeMin()==1) //sinon + si attaque au CAC, on riposte
        {
            degatsAtt= 0.7*getDegats(pVictime, uniteA);
            if(faireDegats(uniteA, degatsAtt))
            {
                uniteA.addExperience(Unite.EXPERIENCE_DONNEE_PAR_NIVEAU*(uniteA.getLvl()+1));
                estMort(uniteA);
            }
        }
        uniteA.attaque(true);
        uniteA.deplacee(true);
        uniteA=null;
    }
    
    public int distance(int dX, int dY, int aX, int aY) // renvoie la distance entre (dX,dY) et (aX,aY)
    {
        return Math.abs(dX-aX) + Math.abs(dY-aY);
    }
    
    public int distance(Unite u1, Unite u2)
    {
        return distance(u1.getX(), u1.getY(), u2.getX(), u2.getY());
    }
    
    public boolean faireDegats(Unite cible, double degats) // retourne vrai si la cible meurt
    {
        cible.setPointDeVie(cible.getPointDeVie() - (int)degats);
        if(cible.getPointDeVie()<=0){return true;}else{return false;}
    }
    
    public double getDegats(Unite a, Unite v) // a= attaquant, v= cible
    {
    	return ((a.getAttaque().getDegats()*a.getAttaque().efficacite.get(v.getType()))*(100-(TypeTerrain.bonusCouverture*Slatch.partie.getTerrain()[v.getX()][v.getY()].getType().getCouverture()))/100)*((double)a.getPV()/(double)a.getPVMax());
    }
    
    public void estMort(Unite unite)
    {
        Slatch.partie.getTerrain()[unite.getX()][unite.getY()].setPV(Slatch.partie.getTerrain()[unite.getX()][unite.getY()].getType().getPVMax());
        Slatch.partie.getTerrain()[unite.getX()][unite.getY()].setUnite(null);
        Slatch.partie.getJoueur(unite.getJoueur()).addNbrUniteMort();
        MainActivity.view.invalidate();
        if(!Slatch.partie.getJoueur(unite.getJoueur()).estUneIA() || unite.getJoueur()!=Slatch.partie.getJoueurActuel())
        {
            Slatch.partie.getJoueur(unite.getJoueur()).getListeUnite().remove(unite);
        }
        
        if(Slatch.partie.getJoueur(unite.getJoueur()).getListeUnite().isEmpty())
        {
            Slatch.partie.getJoueur(unite.getJoueur()).mourrir();
            Slatch.partie.gagner();
        }
    }
    
    
    public void modeAttaque(int pX, int pY)
    {
        uniteA = Slatch.partie.getTerrain()[pX][pY].getUnite();
        affichePorteeAttaque(uniteA, false);
    }
    
    public void affichePorteeAttaque(Unite unite, boolean soin)
    {
    	int x = unite.getX();
        int y = unite.getY();

        for(int i=0; i<Slatch.partie.getanombrecaseX(); i++)
        {
            for(int j=0; j<Slatch.partie.getanombrecaseY(); j++)
            {
                tabAtt[i][j] = false;
            }
        }
        
        for(int i=1; i<=unite.getAttaque().aTypePortee.getPorteeMax();i++)
        {
            for(int j=1; j<=i;j++)
            {
                for(Quad q: signes)
                {
                    int a = i*q.a, b= j*q.b, c=i*q.c, d = j*q.d;
                    if(dansLesBords(x+a+b, y+c+d))
                    {
                        if(ciblePresente(unite,a+b,c+d, soin))
                        {
                            Slatch.partie.getTerrain()[x+a+b][y+c+d].setSurbrillance(true);
                            MainActivity.view.invalidate();
                            tabAtt[x+a+b][y+c+d] = true;
                        }
                    }
                }
            }
        }
        
    }
    
    /*
     * vérifie si une unité se trouve à côté de la case passée en paramètre
     */
    public boolean uniteProche(Unite unite, int pX, int pY)
    {
    	for(Point p: voisins)
        {
            int decX = p.x;
            int decY = p.y;
            if(pX+decX<Slatch.partie.getanombrecaseX() && pX+decX>=0 && pY+decY<Slatch.partie.getanombrecaseY() && pY+decY>=0)
            {
                if(Slatch.partie.getTerrain()[pX+decX][pY+decY].getUnite()!=null)
                {
                    if(Slatch.partie.getTerrain()[pX+decX][pY+decY].getUnite().getJoueur()!=unite.getJoueur())
                    {
                        return true;
                    }
                }
            }
        }
        return false;
    }
    
    public void capture(int pX, int pY)
    {
        Terrain vBatiment= Slatch.partie.getTerrain()[pX][pY];
        uniteA=Slatch.partie.getTerrain()[pX][pY].getUnite();
       
        switch(uniteA.getType())
        {
            case COMMANDO: vBatiment.setPV(vBatiment.getPV()-10); break;
            case DEMOLISSEUR: vBatiment.setPV(vBatiment.getPV()-15);
        }
        
        if(vBatiment.getPV()<=0)
        {
           Slatch.partie.getJoueur(vBatiment.getJoueur()).addNbreBatiment(-1);
           vBatiment.setJoueur(uniteA.getJoueur());
           vBatiment.setPV(vBatiment.getType().getPVMax());
           Slatch.partie.getJoueur(uniteA.getJoueur()).addNbreBatiment(1);
           MainActivity.view.invalidate();
        }
        uniteA.attaque(true);
        uniteA.deplacee(true);
        uniteA=null;
    }
}
